﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;

namespace Doo.Caching
{
    /// <summary>
    /// 内容缓存（为一个生命周期）
    /// </summary>
    public class ContentCache
    {
        /// <summary>
        /// 客户端和服务器端内容版本是否匹配
        /// </summary>
        public bool MatchETag;
        /// <summary>
        /// 当前服务器端版本
        /// </summary>
        public string Version;
        /// <summary>
        /// 服务器端静态键
        /// </summary>
        string[] ServerStaticKeys;
        /// <summary>
        /// 客户端静态键
        /// </summary>
        string[] ClientStaticKeys;
        /// <summary>
        /// 预处理时期的全局版本
        /// </summary>
        internal long PreProcessVersion;
        HttpContext context;
        /// <summary>
        /// 用静态键初始化
        /// </summary>
        /// <param name="serverstatickeys"></param>
        /// <param name="clientstatickeys"></param>
        public ContentCache(HttpContext context, string[] serverstatickeys, string[] clientstatickeys)
        {
            this.context = context;
            ServerStaticKeys = serverstatickeys;
            ClientStaticKeys = clientstatickeys;
        }

        internal void ResetKeys(string[] serverstatickeys, string[] clientstatickeys)
        {
            ServerStaticKeys = serverstatickeys;
            ClientStaticKeys = clientstatickeys;
        }

        public bool EnableCache { get; set; }

        /// <summary>
        /// 获取当前内容缓存
        /// </summary>
        public static ContentCache Current
        {
            get
            {
                return HttpContext.Current.Items[PageCacheHelper.cck] as ContentCache;
            }
        }
        /// <summary>
        /// 处理要发送到客户端的版本[停用]
        /// </summary>
        internal void ProcessClientVersions()
        {
            StringBuilder sb = new StringBuilder();
            foreach (var i in ClientStaticKeys)
            {
                long v;
                if (!PageCacheHelper.Versions.TryGetValue(i, out v)) v = 0;
                sb.AppendFormat("{0}={1}", i, v);
            }
            if (sb.Length > 0) sb.Length--;
            context.Response.Cookies["pageversions"].Value = sb.ToString();
        }
        /// <summary>
        /// 处理输出缓存
        /// </summary>
        internal void ProcessOutputCache()
        {
            foreach (string k in ServerStaticKeys)
            {
                context.Response.AddCacheItemDependency(PageCacheHelper.GetDependenceItem(context, k));
            }
        }

        #region 临时动态键

        internal HashSet<string[]> ClientKeys = new HashSet<string[]>();
        internal HashSet<string[]> ServerKeys = new HashSet<string[]>();

        /// <summary>
        /// 添加客户端缓存依赖动态键
        /// </summary>
        /// <param name="keys"></param>
        public void AddClientCacheKey(string[] keys)
        {
            var ks = ClientKeys;
            if (!ks.Contains(keys)) ks.Add(keys);
        }

        /// <summary>
        /// 添加服务器端缓存依赖动态键
        /// </summary>
        /// <param name="keys"></param>
        public void AddServerCacheKey(string[] keys)
        {
            var ks = ServerKeys;
            if (!ks.Contains(keys)) ks.Add(keys);
        }

        #endregion

    }

    /// <summary>
    /// 同页定义（非实例）绑定的缓存
    /// </summary>
    public class PageCacheHelper
    {
        /// <summary>
        /// 当前内容是否为网页
        /// </summary>
        public bool ispage;
        /// <summary>
        /// 当前内容是否为服务
        /// </summary>
        public bool isservice;

        /// <summary>
        /// 此实例是否是临时创建的
        /// </summary>
        public bool IsTemp;

        internal const string k = "Doo.Caching";
        internal const string cck = "Doo.Caching.CC";

        /// <summary>
        /// 客户器端缓存依赖动态键集合
        /// </summary>
        public HashSet<string[]> ClientKeys = new HashSet<string[]>();

        /// <summary>
        /// 服务器端缓存依赖动态键集合
        /// </summary>
        public HashSet<string[]> ServerKeys = new HashSet<string[]>();

        public ContentCache CreateContentCache(HttpContext context)
        {
            ContentCache cc = new ContentCache(context, GetKeys(ServerKeys), GetKeys(ClientKeys));
            context.Items[cck] = cc;
            return cc;
        }

        /// <summary>
        /// 依赖存储词典
        /// </summary>
        static Dictionary<string, string> dependencies = new Dictionary<string, string>();

        /// <summary>
        /// 获取静态键对应的依赖Key
        /// </summary>
        /// <param name="skey">静态键名</param>
        /// <param name="autonew">是否自动创建新依赖</param>
        /// <returns></returns>
        internal static string GetDependenceItem(HttpContext context, string skey)
        {
            if (dependencies.ContainsKey(skey)) return dependencies[skey];
            string cachekey = "Doo.Caching.Key." + skey;
            context.Cache.Insert(cachekey, true);
            dependencies.Add(skey, cachekey);
            return cachekey;
        }

        internal static void RemoveDependenceItem(HttpContext context, string skey)
        {
            if (!dependencies.ContainsKey(skey)) return;
            string cachekey = "Doo.Caching.Key." + skey;
            context.Cache.Remove(cachekey);
            dependencies.Remove(skey);
        }

        /// <summary>
        /// 获取当前请求内容的缓存状态
        /// </summary>
        public static PageCacheHelper Current { get { return HttpContext.Current.Items[k] as PageCacheHelper; } }

        internal static Dictionary<string, PageCacheHelper> Caches = new Dictionary<string, PageCacheHelper>();

        /// <summary>
        /// 动态键转换委托
        /// </summary>
        public static Dictionary<string, Func<string>> KeyGetter = new Dictionary<string, Func<string>>();

        /// <summary>
        /// 静态键版本集合
        /// </summary>
        public static Dictionary<string, long> Versions = new Dictionary<string, long>();

        /// <summary>
        /// 全局版本
        /// </summary>
        public static long GlobalVersion = 0;

        /// <summary>
        /// 更新静态键对应版本及全局版本
        /// </summary>
        /// <param name="key"></param>
        public static void UpdateVersion(string key)
        {
            GlobalVersion++;
            if (Versions.ContainsKey(key)) Versions[key] = GlobalVersion;
            else Versions.Add(key, GlobalVersion);

            RemoveDependenceItem(HttpContext.Current, key);
        }

        /// <summary>
        /// 获取静态键值中的最大版本
        /// </summary>
        /// <param name="keys"></param>
        /// <returns></returns>
        public static long GetMaxVersion(string[] keys)
        {
            long v = 0;
            foreach (string k in keys)
            {
                if (!Versions.ContainsKey(k)) continue;
                long kv = Versions[k];
                if (v >= kv) continue;
                v = kv;
            }
            return v;
        }

        public static string GetKey(string k)
        {
            Func<string> keygetter;
            if (KeyGetter.TryGetValue(k, out keygetter) == false) return k;
            return keygetter();
        }

        public static string GetKey(string[] keys)
        {
            Func<string> keygetter;
            StringBuilder sb = new StringBuilder();
            foreach (string k in keys)
            {
                if (KeyGetter.TryGetValue(k, out keygetter))
                {
                    sb.Append(keygetter());
                }
                else
                {
                    sb.Append(k);
                }
                sb.Append('_');
            }
            sb.Length--;
            return sb.ToString();
        }

        /// <summary>
        /// 转换键为实际值
        /// </summary>
        /// <param name="keyset"></param>
        /// <returns></returns>
        public static string[] GetKeys(HashSet<string[]> keyset)
        {
            List<string> keylist = new List<string>();
            foreach (var i in keyset)
            {
               keylist.Add(GetKey(i));
            }
            return keylist.ToArray();
        }
        /// <summary>
        /// 此定义依附的路径
        /// </summary>
        public string FilePath;
    }

    public class ContentCacheModule : IHttpModule
    {
        public void Dispose()
        {

        }

        private HttpApplication context;

        public void Init(HttpApplication context)
        {
            this.context = context;
            context.ResolveRequestCache += new EventHandler(context_ResolveRequestCache);
            context.PostRequestHandlerExecute += new EventHandler(context_UpdateRequestCache);
        }

        void context_ResolveRequestCache(object sender, EventArgs e)
        {
            string f = context.Request.AppRelativeCurrentExecutionFilePath;
            bool ispage = f.EndsWith(".aspx");
            bool isservice = f.EndsWith(".asmx");
            if (!ispage && !isservice) return;

            PageCacheHelper cs;
            if (PageCacheHelper.Caches.ContainsKey(f)) cs = PageCacheHelper.Caches[f];
            else { cs = new PageCacheHelper { ispage = ispage, isservice = isservice, FilePath = f, IsTemp = true }; }
            context.Context.Items[PageCacheHelper.k] = cs;
            var cache = cs.CreateContentCache(HttpContext.Current);

            if (cs.ServerKeys.Count > 0)
            {
                string ifnonematch = context.Request.Headers["If-None-Match"];
                string[] keys = PageCacheHelper.GetKeys(cs.ServerKeys);
                string maxver = PageCacheHelper.GetMaxVersion(keys).ToString();
                //1.ETag , send 304
                if (ifnonematch == maxver)
                {
                    context.Response.StatusCode = 304;
                    cache.MatchETag = true;
                    context.CompleteRequest();
                    cache.EnableCache = true;
                    return;
                }
                cache.Version = maxver;
                cache.PreProcessVersion = PageCacheHelper.GlobalVersion;
            }
        }

        /// <summary>
        /// 设置缓存
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void context_UpdateRequestCache(object sender, EventArgs e)
        {
            if (!context.Context.Items.Contains(PageCacheHelper.k)) return;

            ContentCache cc = context.Context.Items[PageCacheHelper.cck] as ContentCache;

            PageCacheHelper cs = PageCacheHelper.Current;
            //如果是首次访问即cc通过了全部流程后还是为禁用缓存，则表示内容并未启用缓存
            if (cc.EnableCache == false && cs.IsTemp) return;
            //表示启用了缓存
            if (cs.IsTemp && cc.EnableCache) { PageCacheHelper.Caches.Add(cs.FilePath, cs); cs.IsTemp = false; }

            if (!cc.MatchETag)
            {
                //重置动态键
                cs.ServerKeys = cc.ServerKeys;
                cs.ClientKeys = cc.ClientKeys;
                cc.ResetKeys(PageCacheHelper.GetKeys(cc.ServerKeys), PageCacheHelper.GetKeys(cc.ClientKeys));
                if (cc.PreProcessVersion != PageCacheHelper.GlobalVersion || string.IsNullOrEmpty(cc.Version))//大版本号已更新
                {
                    cc.Version = PageCacheHelper.GetMaxVersion(PageCacheHelper.GetKeys(cs.ServerKeys)).ToString();
                }
                context.Response.Cache.SetCacheability(HttpCacheability.Public);
                context.Response.Cache.SetETag(cc.Version);
            }

            cc.ProcessOutputCache();
        }



    }
}
